  #!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import json
import time
import threading
import subprocess
import webbrowser
from datetime import datetime
import cv2
import numpy as np
from flask import Flask, render_template_string, request, jsonify, Response
import simplejpeg
from driver.orbbec_img import *

# 创建Flask应用
app = Flask(__name__)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = os.path.join(BASE_DIR, "..", "model", "binary.json")
config_path = os.path.abspath(CONFIG_FILE)


# 全局变量
current_camera = None
camera_lock = threading.Lock()
video_devices = {}
current_config = {"black": {"lower": 88, "upper": 255}}
current_group = "black"
camera_thread = None
camera_running = False

def find_specific_video_devices():
    """
    查找特定的视频设备 (Linux上使用v4l2-ctl)。
    在非Linux系统上，此函数可能需要调整或使用其他方法。
    返回一个字典，键是自定义的相机名 (如 'cap_main', 'cap_aux')，值是设备路径 (如 '/dev/video0')。
    """
    valid_devices = {}
    try:
        # 尝试执行 v4l2-ctl 命令
        process = subprocess.Popen(['v4l2-ctl', '--list-devices'],
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        stdout, stderr = process.communicate(timeout=5) # 添加超时
        devices_output = stdout.decode('utf-8').strip()

        if process.returncode != 0 or not devices_output:
            print(f"警告: 'v4l2-ctl --list-devices' 执行失败或无输出。Stderr: {stderr.decode('utf-8').strip()}")
            # 在这里可以尝试备用方法，或者直接返回空
            #如果v4l2-ctl失败，我们尝试使用默认的0和1作为备选
            if not valid_devices: # 只有当v4l2-ctl完全没找到时
                print("尝试使用默认索引 0 和 1 作为备选相机。")
                if cv2.VideoCapture(0).isOpened(): valid_devices['cap_generic_0_fallback'] = 0
                if cv2.VideoCapture(1).isOpened(): valid_devices['cap_generic_1_fallback'] = 1
            return valid_devices

        devices_blocks = devices_output.split('\n\n')

        for device_block in devices_blocks:
            if not device_block.strip():
                continue
            lines = device_block.strip().split('\n')
            if len(lines) < 2:
                print(f"警告: 格式错误的设备块: {device_block}")
                continue

            device_name_line = lines[0].strip()
            # 通常设备路径在第二行，并且是该行的第一个词条
            # 例如： /dev/video0
            # 有时可能是：      /dev/video0 (前面有空格)
            device_path = ""
            for line_part in lines:
                if line_part.strip().startswith('/dev/video'):
                    device_path = line_part.strip().split()[0] # 取第一个，以防后面有其他信息
                    break
            
            if not device_path:
                print(f"警告: 未能在块中找到 /dev/video 路径: {device_block}")
                continue

            # 根据设备名称分配键名
            # 注意：这些名称是示例，你需要根据你的摄像头实际名称调整
            if "DF100-720P" in device_name_line or "DECXIN" in device_name_line:
                valid_devices['cap_main'] = device_path
            elif "HD USB Camera" in device_name_line : 
                valid_devices['cap_aux'] = device_path
            elif "Integrated Camera" in device_name_line: # 示例：笔记本内置摄像头
                 if 'cap_main' not in valid_devices: # 优先给主摄像头
                    valid_devices['cap_main'] = device_path
                 elif 'cap_aux' not in valid_devices:
                    valid_devices['cap_aux'] = device_path
            else: # 如果找不到特定相机，则使用通用相机名
                if 'cap_generic_0' not in valid_devices and device_path not in valid_devices.values():
                    valid_devices['cap_generic_0'] = device_path
                elif 'cap_generic_1' not in valid_devices and device_path not in valid_devices.values():
                    valid_devices['cap_generic_1'] = device_path
                # 可以继续添加 cap_generic_2 等

    except FileNotFoundError:
        print("错误: 未找到 v4l2-ctl 命令。请确保已安装 (通常用于Linux)。")
        # 备用逻辑 (同上)
        if not valid_devices:
            print("尝试使用默认索引 0 和 1 作为备选相机。")
            if cv2.VideoCapture(0).isOpened(): valid_devices['cap_generic_0_fallback'] = 0
            if cv2.VideoCapture(1).isOpened(): valid_devices['cap_generic_1_fallback'] = 1
    except subprocess.TimeoutExpired:
        print("错误: 'v4l2-ctl --list-devices' 命令执行超时。")
    except subprocess.CalledProcessError as e:
        print(f"执行 v4l2-ctl 时出错: {e}")
    except Exception as e:
        print(f"在 find_specific_video_devices 中发生意外错误: {e}")

    print(f"找到的视频设备: {valid_devices}")
    return valid_devices

def load_config():
    """加载配置文件"""
    global current_config
 
    # 确保data目录存在
    os.makedirs("data", exist_ok=True)
    
    if os.path.exists(config_path):
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                current_config = json.load(f)
                print(f"配置已加载: {current_config}")
        except Exception as e:
            print(f"加载配置失败: {e}")
            current_config = {"black": {"lower": 88, "upper": 255}}
    else:
        # 创建默认配置
        save_config()

def save_config():
    """保存配置文件"""
    global current_config
    # # 确保data目录存在
    # os.makedirs("data", exist_ok=True)
    
    try:
        with open(config_path, 'w', encoding='utf-8') as f:
            json.dump(current_config, f, indent=2, ensure_ascii=False)
        print(f"配置已保存: {current_config}")
        return True
    except Exception as e:
        print(f"保存配置失败: {e}")
        return False

def init_camera():
    """初始化摄像头"""
    global current_camera

    try:
        
        current_camera = OrbbecCamera(color=True, depth=False)
        current_camera.start()
        print("摄像头初始化成功")
        return True
    except Exception as e:
        print(f"初始化摄像头失败: {e}")
    return False

def camera_reconnect_thread():
    """摄像头重连线程"""
    global current_camera, camera_running
    
    while camera_running:
        if current_camera is None :
            print("摄像头断连，尝试重新连接...")
            if current_camera:
                current_camera.stop()
                current_camera = None
            
            if init_camera():
                print("摄像头重连成功")
            else:
                print("摄像头重连失败，3秒后重试")
        
        time.sleep(3)

def generate_frames():
    """生成视频帧"""
    global current_camera, current_config, current_group
    
    while camera_running:
        if current_camera is None:
            time.sleep(0.1)
            continue
        
        with camera_lock:
            frame,_ = current_camera.read_frames()
        
        if frame is None:
            print("读取帧失败")
            time.sleep(0.1)
            continue
        
        try:
            # 获取当前阈值
            if current_group in current_config:
                lower = current_config[current_group]["lower"]
                upper = current_config[current_group]["upper"]
            else:
                lower, upper = 88, 255
            
            # 转换为灰度图
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            
            # 二值化处理
            _, binary = cv2.threshold(gray, lower, upper, cv2.THRESH_BINARY)
            
            # 将二值化图像转换为3通道以便拼接
            binary_color = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
            
            # 上下拼接原图和二值化图像
            height, width = frame.shape[:2]
            # 调整图像大小以适应显示 - 控制总高度
            display_width = 640
            display_height = int(height * display_width / width)
            
            # 如果单张图像高度太大，进一步缩小
            if display_height > 200:
                display_height = 200
                display_width = int(width * display_height / height)
            
            frame_resized = cv2.resize(frame, (display_width, display_height))
            binary_resized = cv2.resize(binary_color, (display_width, display_height))
            
            # 上下拼接 - 总高度约为400像素
            combined = np.vstack([frame_resized, binary_resized])
            
            # 使用simplejpeg压缩
            try:
                encoded_img = simplejpeg.encode_jpeg(combined, quality=80, colorspace='BGR')
            except:
                # 如果simplejpeg失败，使用cv2编码
                _, encoded_img = cv2.imencode('.jpg', combined, [cv2.IMWRITE_JPEG_QUALITY, 80])
                encoded_img = encoded_img.tobytes()
            
            yield (b'--frame\r\n'
                   b'Content-Type: image/jpeg\r\n\r\n' + encoded_img + b'\r\n')
        
        except Exception as e:
            print(f"生成帧时出错: {e}")
            time.sleep(0.1)

# HTML模板
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图像二值化调节</title>
    <style>
        body {
            margin: 0;
            padding: 10px;
            font-family: Arial, sans-serif;
            background-color: #f0f0f0;
            width: 1024px;
            height: 600px;
            overflow-y: auto;
            box-sizing: border-box;
        }
        
        .container {
            display: flex;
            height: 580px;
            gap: 10px;
            min-height: 580px;
        }
        
        .image-container {
            flex: 1;
            background: white;
            border-radius: 8px;
            padding: 10px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            display: flex;
            flex-direction: column;
            align-items: center;
            overflow-y: auto;
            max-width: 700px;
        }
        
        .controls-container {
            width: 300px;
            background: white;
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            height: fit-content;
            max-height: 580px;
            overflow-y: auto;
        }
        
        .video-feed {
            width: 70%;
            height: auto;
            max-width: 300px;
            border-radius: 4px;
            object-fit: contain;
        }
        
        .control-group {
            margin-bottom: 20px;
        }
        
        .control-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
            color: #333;
        }
        
        .slider-container {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 10px;
        }
        
        .slider {
            flex: 1;
            -webkit-appearance: none;
            height: 6px;
            border-radius: 3px;
            background: #ddd;
            outline: none;
        }
        
        .slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #4CAF50;
            cursor: pointer;
        }
        
        .slider::-moz-range-thumb {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #4CAF50;
            cursor: pointer;
            border: none;
        }
        
        .value-display {
            width: 50px;
            text-align: center;
            font-weight: bold;
            color: #666;
        }
        
        select, button {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            margin-bottom: 10px;
        }
        
        button {
            background: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
            font-weight: bold;
        }
        
        button:hover {
            background: #45a049;
        }
        
        .exit-btn {
            background: #f44336;
        }
        
        .exit-btn:hover {
            background: #da190b;
        }
        
        .status {
            padding: 10px;
            border-radius: 4px;
            margin-bottom: 10px;
            text-align: center;
        }
        
        .status.success {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        
        .status.error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        
        .image-label {
            font-size: 18px;
            font-weight: bold;
            margin-bottom: 10px;
            color: #333;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="image-container">
            <div class="image-label">实时图像处理</div>
            <img src="/video_feed" class="video-feed" alt="Video Feed">
        </div>
        
        <div class="controls-container">
            <div class="control-group">
                <label for="group-select">阈值组选择:</label>
                <select id="group-select" onchange="changeGroup()">
                    {% for group in config.keys() %}
                    <option value="{{ group }}" {% if group == current_group %}selected{% endif %}>{{ group }}</option>
                    {% endfor %}
                </select>
            </div>
            
            <div class="control-group">
                <label>最小阈值:</label>
                <div class="slider-container">
                    <input type="range" id="lower-slider" class="slider" min="0" max="255" 
                           value="{{ config[current_group]['lower'] }}" oninput="updateThreshold()">
                    <div class="value-display" id="lower-value">{{ config[current_group]['lower'] }}</div>
                </div>
            </div>
            
            <div class="control-group">
                <label>最大阈值:</label>
                <div class="slider-container">
                    <input type="range" id="upper-slider" class="slider" min="0" max="255" 
                           value="{{ config[current_group]['upper'] }}" oninput="updateThreshold()">
                    <div class="value-display" id="upper-value">{{ config[current_group]['upper'] }}</div>
                </div>
            </div>
            
            <div class="control-group">
                <label for="new-group">新建阈值组:</label>
                <input type="text" id="new-group" placeholder="输入组名..." style="margin-bottom: 5px;">
                <button onclick="addGroup()">添加组</button>
            </div>
            
            <div id="status" class="status" style="display: none;"></div>
            
            <button onclick="saveAll()">保存所有阈值</button>
            <button onclick="exitApp()" class="exit-btn">退出应用</button>
        </div>
    </div>

    <script>
        let currentGroup = '{{ current_group }}';
        let config = {{ config | tojson }};
        
        function updateThreshold() {
            const lower = document.getElementById('lower-slider').value;
            const upper = document.getElementById('upper-slider').value;
            
            document.getElementById('lower-value').textContent = lower;
            document.getElementById('upper-value').textContent = upper;
            
            // 更新配置
            config[currentGroup].lower = parseInt(lower);
            config[currentGroup].upper = parseInt(upper);
            
            // 发送到服务器
            fetch('/update_threshold', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    group: currentGroup,
                    lower: parseInt(lower),
                    upper: parseInt(upper)
                })
            });
        }
        
        function changeGroup() {
            const select = document.getElementById('group-select');
            currentGroup = select.value;
            
            // 更新滑块值
            document.getElementById('lower-slider').value = config[currentGroup].lower;
            document.getElementById('upper-slider').value = config[currentGroup].upper;
            document.getElementById('lower-value').textContent = config[currentGroup].lower;
            document.getElementById('upper-value').textContent = config[currentGroup].upper;
            
            // 通知服务器当前组变更
            fetch('/change_group', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({group: currentGroup})
            });
        }
        
        function addGroup() {
            const newGroupName = document.getElementById('new-group').value.trim();
            if (!newGroupName) {
                showStatus('请输入组名', 'error');
                return;
            }
            
            if (config[newGroupName]) {
                showStatus('组名已存在', 'error');
                return;
            }
            
            // 添加新组
            config[newGroupName] = {lower: 88, upper: 255};
            
            // 更新选择框
            const select = document.getElementById('group-select');
            const option = document.createElement('option');
            option.value = newGroupName;
            option.textContent = newGroupName;
            select.appendChild(option);
            
            // 选择新组
            select.value = newGroupName;
            changeGroup();
            
            document.getElementById('new-group').value = '';
            showStatus('组添加成功', 'success');
        }
        
        function saveAll() {
            fetch('/save_config', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(config)
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    showStatus('保存成功', 'success');
                } else {
                    showStatus('保存失败', 'error');
                }
            })
            .catch(error => {
                showStatus('保存失败', 'error');
            });
        }
        
        function exitApp() {
            if (confirm('确定要退出应用吗？')) {
                fetch('/exit', {method: 'POST'})
                .then(() => {
                    window.close();
                });
            }
        }
        
        function showStatus(message, type) {
            const status = document.getElementById('status');
            status.textContent = message;
            status.className = 'status ' + type;
            status.style.display = 'block';
            
            setTimeout(() => {
                status.style.display = 'none';
            }, 3000);
        }
    </script>
</body>
</html>
"""

@app.route('/')
def index():
    """主页面"""
    return render_template_string(HTML_TEMPLATE, 
                                config=current_config, 
                                current_group=current_group)

@app.route('/video_feed')
def video_feed():
    """视频流"""
    return Response(generate_frames(),
                   mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/update_threshold', methods=['POST'])
def update_threshold():
    """更新阈值"""
    global current_config
    
    data = request.get_json()
    group = data.get('group')
    lower = data.get('lower')
    upper = data.get('upper')
    
    if group in current_config:
        current_config[group]['lower'] = lower
        current_config[group]['upper'] = upper
    else:
        current_config[group] = {'lower': lower, 'upper': upper}
    
    return jsonify({'success': True})

@app.route('/change_group', methods=['POST'])
def change_group():
    """切换当前组"""
    global current_group
    
    data = request.get_json()
    current_group = data.get('group')
    
    return jsonify({'success': True})

@app.route('/save_config', methods=['POST'])
def save_config_route():
    """保存配置"""
    global current_config
    
    data = request.get_json()
    current_config = data
    
    success = save_config()
    return jsonify({'success': success})

@app.route('/exit', methods=['POST'])
def exit_app():
    """退出应用"""
    global camera_running
    
    camera_running = False
    
    if current_camera:
        current_camera.stop()
    
    # 在新线程中关闭Flask应用
    def shutdown():
        time.sleep(1)
        os._exit(0)
    
    threading.Thread(target=shutdown).start()
    return jsonify({'success': True})

def open_browser():
    """自动打开浏览器"""
    time.sleep(2)  # 等待服务器启动
    webbrowser.open('http://localhost:5000')

if __name__ == '__main__':
    print("正在启动图像二值化调节应用...")
    
    # 加载配置
    load_config()
    
    # 初始化摄像头
    camera_running = True
    if not init_camera():
        print("警告: 未能初始化摄像头，将在运行时尝试重连")
    
    # 启动摄像头重连线程
    camera_thread = threading.Thread(target=camera_reconnect_thread, daemon=True)
    camera_thread.start()
    
    # 自动打开浏览器
    browser_thread = threading.Thread(target=open_browser, daemon=True)
    browser_thread.start()
    
    print("应用启动成功!")
    print("访问地址: http://localhost:5000")
    print("按 Ctrl+C 退出应用")
    
    try:
        # 启动Flask应用
        app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)
    except KeyboardInterrupt:
        print("\n正在关闭应用...")
        camera_running = False
        if current_camera:
            current_camera.stop()
    finally:
        cv2.destroyAllWindows()